if (!window.Puzzle) {
window.Puzzle = (function () {
    let dimension = 3, table, data, parent, steps = 0, settings = {}, preview;
    let point = {x: -1, y: -1};

    function createGamePanel (table, parent, data) {
        if (table) {
            if (table) {
                parent.removeChild (table);
            }
        }

        table = document.createElement ('table');
        parent.appendChild (table);
        table.className = 'puzzle-table';

        let index = 0;
        for (let i = 0; i < dimension; i ++) {
            let row = table.insertRow (-1);
            for (let j = 0; j < dimension; j ++) {
                let cell = row.insertCell (-1);
                cell.classList.add ('default');
                cell.onclick = on_cell_clicked;
                let value = data[index];
                cell.setAttribute ('data-index', (index + 1).toString ());

                if (value) {
                    cell.innerText = data [index];
                } else {
                    cell.innerHTML = '&nbsp;'
                    point = {x: j, y: i};
                    // cell.setAttribute ('data-index', '0');
                }
                index ++;
            }
        }
        find_active_cells (table);
        return table;
    }

    function random (data) {
        for (let i = data.length - 1; i > 0; i --) {
            let index = Math.floor (Math.random () * i);
            let temp  = data[i];
            data[i] = data[index];
            data[index] = temp;
        }
    }

    function on_cell_clicked () {
        if (!this.hasAttribute ('data-clickable')) {
            return;
        }
        let value = this.innerText, target = table.rows[point.y].cells[point.x];
        point.x = this.cellIndex;
        point.y = this.parentElement.rowIndex;
        this.innerText = '';
        target.innerText = value;
        steps ++;
        if ('onstep' in settings && (typeof settings.onstep === 'function')) {
            settings.onstep (steps);
        }

        if (check_game ()) {
            if ('onsuccess' in settings && (typeof settings.onsuccess === "function")) {
                settings.onsuccess (steps);
            }
            clear_active_cells ();
            return;
        }

        clear_active_cells ();
        find_active_cells (table);
    }

    function find_active_cells (table) {
        let r = point.y - 1, c;
        if (r >= 0) {
            mark_active (table.rows[r].cells [point.x]);
        }
        r = point.y + 1;
        if (r < dimension) {
            mark_active (table.rows[r].cells [point.x]);
        }
        c = point.x - 1;
        if (c >= 0) {
            mark_active (table.rows[point.y].cells[c]);
        }
        c = point.x + 1;
        if (c < dimension) {
            mark_active (table.rows[point.y].cells[c]);
        }
    }

    function mark_active (cell) {
        cell.classList.add ('active');
        cell.setAttribute ('data-clickable', 'true');
    }

    function clear_active_cells () {
        let cells = document.querySelectorAll ('.puzzle-table td.active');
        for (let i = 0; i < cells.length; i ++) {
            cells[i].classList.remove ('active');
            cells[i].removeAttribute ('data-clickable');
        }
    }

    function copy (src, dst) {
        for (let k in src) if (src.hasOwnProperty (k)) {
            dst[k] = src[k];
        }
    }

    function check_game () {
        let last  = settings.dimension - 1,
            index;
        if (point.x !== last || point.y !== last) {
            return false;
        }

        let cells = document.querySelectorAll ('td[data-index]'), value;
        for (let i = 0; i < cells.length - 1; i ++) {
            index = parseInt (cells[i].getAttribute ('data-index'), 10);
            if (cells[i].innerText !== '') {
                value = parseInt (cells[i].innerText);
            } else {
                value = 0;
            }

            if (index !== value) {
                return false;
            }
        }
        return true;
    }

    return {
        init : function (options) {
            console.log ('initialing game ...')
            copy (options = options || {}, settings);
            if ('dimension' in options) {
                try {
                    dimension = parseInt (options.dimension, 10);
                } catch (E) {}
            }
            if (dimension < 3) {
                dimension = 3;
            }
            if (dimension) {
                settings.dimension = dimension;
                data = [];
                let count = dimension * dimension;
                for (let i = 0; i < count; i ++) {
                    data [i] = i + 1;
                }
                data [count - 1] = 0;
                let preview_parent;
                if ('preview' in options) {
                    preview_parent = document.getElementById (options.preview);
                } else {
                    preview_parent = document.body;
                }
                preview = createGamePanel (preview, preview_parent, data);

                random (data);

                if ('parent' in options) {
                    parent = document.getElementById (options.parent);
                } else {
                    parent = document.body;
                }

                table = createGamePanel (table, parent, data);
            }
            steps = 0;
        }
    };
})();
}